package com.siyoumi.component.api;

import com.siyoumi.config.SysConfig;
import com.siyoumi.exception.EnumSys;
import com.siyoumi.util.XLog;
import com.siyoumi.util.XStr;
import com.siyoumi.validator.XValidator;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

//微信支付接口
@Slf4j
public class XWxApiPay {
    static private Map<String, PrivateKey> mapPrivateKey = new HashMap<>();
    static private Map<String, X509Certificate> mapCertificate = new HashMap<>();
    static private final Object lock = new Object();

    @SneakyThrows
    static public PrivateKey loadPrivateKey(String mchId) {
        if (mapPrivateKey.containsKey(mchId)) {
            return mapPrivateKey.get(mchId);
        }
        if (XStr.isNullOrEmpty(SysConfig.getIns().getCertPath())) {
            XValidator.err(EnumSys.ENV_ERR.getR("证书路径未配置"));
        }

        synchronized (lock) {
            //C:\zzz_wx\_cert\1609208339_key.pem
            String pkFilePath = XStr.concat(SysConfig.getIns().getCertPath(), mchId, "_key.pem");

            String content = Files.readString(Paths.get(pkFilePath));
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");

            KeyFactory kf = KeyFactory.getInstance("RSA");
            PrivateKey pk = kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
            log.debug("init: {}", mchId);
            mapPrivateKey.put(mchId, pk);
        }

        return mapPrivateKey.get(mchId);
    }

    public static X509Certificate loadCertificate(String mchId) {
        return loadCertificate(mchId, "cert");
    }

    /**
     * 1707657972_cert
     * 转
     * mchId:1707657972
     * suffix:cert
     *
     * @param mchId  商户号
     * @param suffix 证书名称后缀
     */
    @SneakyThrows
    public static X509Certificate loadCertificate(String mchId, String suffix) {
        String key = mchId + suffix;
        if (mapCertificate.containsKey(key)) {
            return mapCertificate.get(key);
        }

        synchronized (lock) {
            //1707657972_cert.pem
            String pkFilePath = XStr.concat(SysConfig.getIns().getCertPath(), mchId, "_", suffix, ".pem");
            FileInputStream inputStream = new FileInputStream(pkFilePath);
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
            inputStream.close();

            cert.checkValidity();
            mapCertificate.put(key, cert);
        }

        return mapCertificate.get(key);
    }

    /**
     * 获取证书序列号
     */
    static public String getSerialNumber(X509Certificate certificate) {
        return certificate.getSerialNumber().toString(16).toUpperCase();
    }


    /**
     * 签名
     *
     * @param pk           密钥对象
     * @param method       请求方法  GET  POST PUT DELETE 等
     * @param canonicalUrl 例如  https://api.mch.weixin.qq.com/v3/pay/transactions/app?version=1 ——> /v3/pay/transactions/app?version=1
     * @param timestamp    当前时间戳
     * @param nonceStr     随机字符串
     * @param body         请求体 GET 为 "" POST 为 JSON
     * @return 签名
     */
    @SneakyThrows
    static public String sign(PrivateKey pk, String method, String canonicalUrl, long timestamp, String nonceStr, String body) {
        String signatureStr = Stream.of(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body)
                .collect(Collectors.joining("\n", "", ""));
        log.debug(signatureStr);

        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(pk);
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));

        return Base64.getEncoder().encodeToString(sign.sign());
    }


    /**
     * 生成Token.
     *
     * @param mchId     商户号
     * @param nonceStr  随机字符串
     * @param timestamp 时间戳
     * @param serialNo  证书序列号
     * @param sign      签名
     * @return the string
     */
    static public String token(String mchId, String nonceStr, long timestamp, String serialNo, String sign) {
        return XStr.concat("mchid=\"", mchId, "\","
                , "nonce_str=\"", nonceStr, "\","
                , "timestamp=\"", String.valueOf(timestamp), "\","
                , "serial_no=\"", serialNo, "\","
                , "signature=\"", sign, "\""
        );
    }


    /**
     * rsa加密文本
     *
     * @param txt
     */
    @SneakyThrows
    static public String encryptTxt(String mchId, String txt) {
        X509Certificate certificate = XWxApiPay.loadCertificate(mchId);
        //String serialNumber = XWxApiPay.getSerialNumber(certificate);
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
            byte[] data = txt.getBytes(StandardCharsets.UTF_8);
            byte[] cipherdata = cipher.doFinal(data);
            return Base64.getEncoder().encodeToString(cipherdata);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的公钥", e);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
        }
    }
}
